package com.wjc.train.business.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wjc.train.business.entity.*;
import com.wjc.train.business.enums.ConfirmOrderStatusEnum;
import com.wjc.train.business.enums.SeatColEnum;
import com.wjc.train.business.enums.SeatTypeEnum;
import com.wjc.train.business.repository.ConfirmOrderRepository;
import com.wjc.train.business.repository.DailyTrainCarriageRepository;
import com.wjc.train.business.repository.DailyTrainTicketRepository;
import com.wjc.train.business.req.ConfirmOrderQueryReq;
import com.wjc.train.business.req.ConfirmOrderSaveReq;
import com.wjc.train.business.resp.ConfirmOrderQueryResp;
import com.wjc.train.business.service.IConfirmOrderService;
import com.wjc.train.common.enums.LogicEnum;
import com.wjc.train.common.enums.RedisKeyPreEnum;
import com.wjc.train.common.exception.BusinessException;
import com.wjc.train.common.exception.BusinessExceptionCode;
import com.wjc.train.common.untils.CopyUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 确认订单表 服务实现类
 * </p>
 *
 * @author Guessed
 * @since 2023-08-15
 */
@Service
@Slf4j
public class ConfirmOrderServiceImpl implements IConfirmOrderService {

    @Resource
    private ConfirmOrderRepository confirmorderRepository;

    @Resource
    private DailyTrainTicketRepository dailytrainticketRepository;

    @Resource
    private DailyTrainSeatServiceImpl dailyTrainSeatService;

    @Resource
    private AfterConfirmOrderServiceImpl afterConfirmOrderService;

    @Resource
    private DailyTrainCarriageRepository dailytraincarriageRepository;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(ConfirmOrderSaveReq confirmorderSaveReq) {
        ConfirmOrder confirmorder = CopyUtil.copy(confirmorderSaveReq, ConfirmOrder.class);
        confirmorderRepository.saveOrUpdate(confirmorder);
    }

    @Override
    public IPage<ConfirmOrderQueryResp> queryList(IPage<ConfirmOrderQueryResp> page, ConfirmOrderQueryReq confirmorderQueryReq) {
        return confirmorderRepository.queryList(page, confirmorderQueryReq);
    }

    @Override
    public void delete(Long id) {
        confirmorderRepository.getBaseMapper().deleteById(id);
    }

    @Override
    @SentinelResource(value = "doConfirm",blockHandler = "doConfirmBlock")
    public void doConfirm(ConfirmOrderSaveReq confirmorderSaveReq,String messageKey) {
        Date date = confirmorderSaveReq.getDate();
        String trainCode = confirmorderSaveReq.getTrainCode();
        //加锁
        String key = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(date) + "-" + trainCode;
        RLock rLock = redissonClient.getLock(key);
        try{
            //三个参数的 waitTime time timeUnit 不带看门狗(不会自动续期)
            //两个参数的 waitTime  timeUnit 带看门狗(会自动续期),默认30秒过期后自动续期
            boolean tryLock = rLock.tryLock(20, TimeUnit.SECONDS);
            if (!tryLock) {
                log.info("抢锁失败,订单失败");
                updateFailedOrderByStatus(confirmorderSaveReq.getOrderId(),
                        confirmorderSaveReq.getMemberId(),
                        date, trainCode,ConfirmOrderStatusEnum.FAILURE.getCode());
                return;
            }

            //从数据库查出订单信息
            ConfirmOrder dBorder = confirmorderRepository.getOne(new QueryWrapper<ConfirmOrder>()
                            .eq("`date`", date)
                            .eq("id",confirmorderSaveReq.getOrderId())
                            .eq("train_code", trainCode)
                            .eq("member_id", confirmorderSaveReq.getMemberId())
                            .eq("status", ConfirmOrderStatusEnum.INIT.getCode()),false);

            if(ObjectUtil.isEmpty(dBorder)){
                log.info("找不到原始订单,结束");
                return;
            }

            //更新订单为处理中
            confirmorderRepository.lambdaUpdate()
                    .eq(ConfirmOrder::getId,dBorder.getId())
                    .set(ConfirmOrder::getStatus,ConfirmOrderStatusEnum.PENDING.getCode()).update();

            //查出余票数
            QueryWrapper<DailyTrainTicket> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("date", date)
                    .eq("is_deleted", LogicEnum.NOT_DELETED.getCode())
                    .eq("train_code", trainCode)
                    .eq("start", confirmorderSaveReq.getStart())
                    .eq("end", confirmorderSaveReq.getEnd());

            DailyTrainTicket trainTicket = dailytrainticketRepository.getOne(queryWrapper);
            if (ObjectUtil.isEmpty(trainTicket)) {
                throw new BusinessException("未查询到对应车次的车票信息!");
            }
            List<OrderTickets> orderTickets = confirmorderSaveReq.getTickets();
            if (CollectionUtil.isEmpty(orderTickets)) {
                throw new BusinessException("未传入车票信息");
            }
            //预扣减余票数量,并判断余票是否足够
            perReduceTickets(trainTicket, orderTickets,dBorder);
            //最终选座的结果
            List<DailyTrainSeat> finalSeatList = new ArrayList<>();
            OrderTickets firstTicket = orderTickets.get(0);
            //计算座位偏移值
            //计算相对第一个座位的偏移值
            //如: 选择的是C1,D1 则偏移值是:[0,5]
            //如: 选择的是A1,B1,C1, 则偏移值是:[0,1,2]
            //判断是否选座
            if (StringUtils.isNotBlank(firstTicket.getSeat())) {
                log.info("本次要选座");
                //查出本次选座的座位类型有哪些列
                List<SeatColEnum> colsSeatList = SeatColEnum.getColsByType(firstTicket.getSeatTypeCode());
                log.info("本次选座的列为:{}", colsSeatList);
                //组装成两排,如[A1,C1,D1,F1,A2,C2,D2,F2]
                List<String> referSeatList = new ArrayList<>();
                for (int i = 1; i <= 2; i++) {
                    for (SeatColEnum colEnum : colsSeatList) {
                        referSeatList.add(colEnum.getCode() + i);
                    }
                }
                log.info("用于参照的两排座位:{}", referSeatList);
                List<Integer> absoluteOffset = new ArrayList<>();
                List<Integer> relativeOffset = new ArrayList<>();
                //先算出绝对偏移值即在数组中的下标,如C1,D2为[1,6];减去第一位的值得到[0,5]
                for (OrderTickets ticket : orderTickets) {
                    int index = referSeatList.indexOf(ticket.getSeat());
                    absoluteOffset.add(index);
                }
                log.info("计算得到所有座位的绝对偏移值:{}", absoluteOffset);
                for (Integer index : absoluteOffset) {
                    int offSet = index - absoluteOffset.get(0);
                    relativeOffset.add(offSet);
                }
                log.info("计算得到所有座位的相对第一个座位的偏移值:{}", relativeOffset);
                //开始挑选座位
                //spilt A1->A
                getSeat(finalSeatList, date,
                        trainCode,
                        firstTicket.getSeatTypeCode().toString(),
                        firstTicket.getSeat().split("")[0],
                        relativeOffset,
                        (int) trainTicket.getStartIndex(),
                        (int) trainTicket.getEndIndex());
            } else {
                log.info("本次不选座");
                for (OrderTickets ticket : orderTickets) {
                    getSeat(finalSeatList, date,
                            trainCode,
                            ticket.getSeatTypeCode().toString(),
                            null,
                            null,
                            (int) trainTicket.getStartIndex(),
                            (int) trainTicket.getEndIndex());
                }
            }
            log.info("最终选座列表：{}", finalSeatList);

            //更新座位售卖信息
            try {
                afterConfirmOrderService.afterDoConfirm(trainTicket,finalSeatList,orderTickets,dBorder);
            } catch (Exception e) {
                log.error("购票出现异常",e);
                throw new BusinessException("购票出现异常!");
            }

            //更新消息状态
            log.info("更新消息状态为成功:{}",messageKey);
            stringRedisTemplate.opsForValue().set(messageKey,ConfirmOrderStatusEnum.SUCCESS.getCode());
        } catch (BusinessException e) {
            //发生异常删除消息key
            log.error("发生异常删除消息key:{}",messageKey);
            stringRedisTemplate.delete(messageKey);
            throw new BusinessException(e.getMessage());
        }catch (InterruptedException e) {
                log.error("锁被中断");
                throw new RuntimeException(e);
        } finally {
            //解锁,只有自己的锁才能删
            if(rLock.isLocked() && rLock.isHeldByCurrentThread()){
                rLock.unlock();
            }
        }


    }

    private void updateFailedOrderByStatus(Long orderId,Long memberId, Date date,
                                           String trainCode,String statusCode) {
        confirmorderRepository.lambdaUpdate()
                .eq(ConfirmOrder::getDate, date)
                .eq(ConfirmOrder::getTrainCode, trainCode)
                .eq(ConfirmOrder::getId, orderId)
                .eq(ConfirmOrder::getMemberId, memberId)
                .eq(ConfirmOrder::getStatus, ConfirmOrderStatusEnum.INIT.getCode())
                .set(ConfirmOrder::getStatus,statusCode).update();
    }

    /**
     * 获取座位
     *
     * @param finalSeatList  最终选座结果
     * @param date           日期
     * @param trainCode      车次编码
     * @param seatType       座位类型
     * @param column         第一个座位列
     * @param relativeOffset 相对座位列表
     */
    private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String column,
                         List<Integer> relativeOffset,
                         Integer startIndex, Integer endIndex) {
        List<DailyTrainSeat> getSeatList = new ArrayList<>();
        //一个车厢一个车厢的获取座位数据
        List<DailyTrainCarriage> trainCarriageList = dailytraincarriageRepository.lambdaQuery()
                .eq(DailyTrainCarriage::getDate, date)
                .eq(DailyTrainCarriage::getTrainCode, trainCode)
                .eq(DailyTrainCarriage::getSeatType, seatType)
                .eq(DailyTrainCarriage::getIsDeleted, LogicEnum.NOT_DELETED.getCode())
                .list();
        if (CollectionUtil.isEmpty(trainCarriageList)) {
            throw new BusinessException("未查询到当日车厢数据!");
        }
        log.info("共查出{}个符合条件的车厢", trainCarriageList.size());
        //查找当日车次的座位
        List<DailyTrainSeat> dailyTrainSeats = dailyTrainSeatService.selectDailyTrainSeatByCode(date, trainCode);
        if (CollectionUtil.isEmpty(dailyTrainSeats)) {
            throw new BusinessException("当日该车次的车座数据为空");
        }
        for (DailyTrainCarriage carriage : trainCarriageList) {
            log.info("开始从车厢{}选座", carriage.getIndex());
            List<DailyTrainSeat> seats
                    = dailyTrainSeats.stream().filter(x -> carriage.getSeatType().equals(x.getSeatType())
                    && carriage.getIndex().equals(x.getCarriageIndex())).toList();
            log.info("车厢:{},座位数:{}", carriage.getIndex(), seats.size());
            getSeatList.clear();
            if (CollectionUtil.isEmpty(seats)) {
                throw new BusinessException("当前车厢: %s 不存在车座", carriage.getIndex());
            }
            for (DailyTrainSeat seat : seats) {
                //判断column,有值的话要比对列号
                Integer seatIndex = seat.getCarriageSeatIndex();
                String col = seat.getSeatCol();
                //判断，不能存在finalList中,否则会被重复选到
                boolean existsFlag = false;
                for (DailyTrainSeat trainSeat : finalSeatList) {
                    //判断
                    if (trainSeat.getId().equals(seat.getId())) {
                        existsFlag = true;
                        break;
                    }
                }
                if (existsFlag) {
                    log.info("座位{}被选中过,不能重复选中", seatIndex);
                    continue;
                }
                if (StringUtils.isBlank(column)) {
                    log.info("无选座");
                } else {
                    if (!column.equals(col)) {
                        log.info("座位{}列值不对,继续判断下一座位.当前列值{},目标列值{}", seatIndex, column, col);
                        continue;
                    }
                }
                if (calSell(seat, startIndex, endIndex)) {
                    log.info("选中座位");
                    getSeatList.add(seat);
                } else {
                    continue;
                }

                //根据relativeOffset选剩下的座位
                boolean isGetAllOffsetSeat = true;
                if (CollectionUtil.isNotEmpty(relativeOffset)) {
                    log.info("有偏移值:{},校验偏移值的座位是否可选", relativeOffset);
                    //从索引1开始，索引0就是当前已选中的票
                    for (int i = 1; i < relativeOffset.size(); i++) {
                        Integer offSet = relativeOffset.get(i);
                        //库中索引从1开始
                        int nextIndex = seatIndex + offSet - 1;
                        //判断溢出
                        if (nextIndex > seats.size()) {
                            log.info("座位{}不可选，超出该车厢座位数", nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }

                        DailyTrainSeat nextSeat = seats.get(nextIndex);
                        if (calSell(nextSeat, startIndex, endIndex)) {
                            log.info("选中座位:{}", nextSeat.getCarriageSeatIndex());
                            getSeatList.add(nextSeat);
                        } else {
                            log.info("座位{}不可选", nextSeat.getCarriageSeatIndex());
                            isGetAllOffsetSeat = false;
                            break;
                        }
                    }
                }
                if (!isGetAllOffsetSeat) {
                    //清空待选座列表
                    getSeatList.clear();
                    continue;
                }
                //保存选好的座位
                finalSeatList.addAll(getSeatList);
                return;
            }
        }
    }

    /**
     * 预扣减库存
     *
     * @param trainTicket  库存车票
     * @param orderTickets 订单车票
     */
    private void perReduceTickets(DailyTrainTicket trainTicket, List<OrderTickets> orderTickets,ConfirmOrder dBorder) {
        for (OrderTickets ticket : orderTickets) {
            SeatTypeEnum seatTypeEnum
                    = SeatTypeEnum.getEnumByCode(String.valueOf(ticket.getSeatTypeCode()));
            if (ObjectUtil.isEmpty(seatTypeEnum)) {
                throw new BusinessException("不存在该座位类型!");
            }
            switch (seatTypeEnum) {
                case RW -> {
                    int rw = trainTicket.getRw() - 1;
                    if (rw < 0) {
                        updateFailedOrderByStatus(dBorder.getId(),dBorder.getMemberId(),trainTicket.getDate(),trainTicket.getTrainCode(),ConfirmOrderStatusEnum.EMPTY.getCode());
                        log.info("软卧余票不足!");
                        return;
                    }
                    trainTicket.setRw(rw);
                }
                case YW -> {
                    int yw = trainTicket.getYw() - 1;
                    if (yw < 0) {
                        updateFailedOrderByStatus(dBorder.getId(),dBorder.getMemberId(),trainTicket.getDate(),trainTicket.getTrainCode(),ConfirmOrderStatusEnum.EMPTY.getCode());
                        log.info("硬卧余票不足!");
                        return;
                    }
                    trainTicket.setYw(yw);
                }
                case EDZ -> {
                    int edz = trainTicket.getEdz() - 1;
                    if (edz < 0) {
                        updateFailedOrderByStatus(dBorder.getId(),dBorder.getMemberId(),trainTicket.getDate(),trainTicket.getTrainCode(),ConfirmOrderStatusEnum.EMPTY.getCode());
                        log.info("二等座余票不足!");
                        return;
                    }
                    trainTicket.setEdz(edz);
                }
                case YDZ -> {
                    int ydz = trainTicket.getYdz() - 1;
                    if (ydz < 0) {
                        updateFailedOrderByStatus(dBorder.getId(),dBorder.getMemberId(),trainTicket.getDate(),trainTicket.getTrainCode(),ConfirmOrderStatusEnum.EMPTY.getCode());
                        log.info("一等座余票不足!");
                        return;
                    }
                    trainTicket.setYdz(ydz);
                }
                default -> throw new BusinessException("不存在该座位类型!");
            }
        }
    }

    /**
     * 计算某座位在区间内是否可卖
     * 例如: sell=10001,本次购买区间站1-4,则区间已售000
     * 起始站是0,所以这个表示0-1卖出去了,第4-5卖出去了
     * 全部都是0，表示这个区间可买，只要有1，就表示区间内已售
     * 若选座后，计算得出购票后的sell为  01110 ,与原sell 10001 进行 按位或 运算得到11111
     */
    private boolean calSell(DailyTrainSeat seat, Integer startIndex, Integer endIndex) {
        String sell = seat.getSell();
        String sellPart = sell.substring(startIndex, endIndex);
        if (Integer.parseInt(sellPart) > 0) {
            log.info("座位{}在本次车站区间{}-{}已售，不可选该座", seat.getCarriageSeatIndex(), startIndex, endIndex);
            return false;
        } else {
            log.info("座位{}在本次车站区间{}-{}未售，可选该座", seat.getCarriageSeatIndex(), startIndex, endIndex);
            //111
            String curSell = sellPart.replace('0', '1');
            //0111
            curSell = StrUtil.fillBefore(curSell, '0', endIndex);
            curSell = StrUtil.fillAfter(curSell, '0', sell.length());
            //按位或 -> 32(10) ->11111(B)
            int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            //避免转换后位数不对
            newSell = StrUtil.fillBefore(newSell, '0', sell.length());
            log.info("座位{}被选中,原售票信息:{},车站区间:{}-{},即:{},最终售票信息:{}", seat.getCarriageSeatIndex(),
                    sell, startIndex, endIndex, curSell, newSell);
            seat.setSell(newSell);
            return true;
        }
    }

    /**
     * 降级方法，需包含限流方法的所有参数和BlockException
     * @param confirmOrderSaveReq
     * @param e
     */
    public void doConfirmBlock(ConfirmOrderSaveReq confirmOrderSaveReq,BlockException e){
        log.info("购票请求被限流:{}",confirmOrderSaveReq);
        throw new BusinessException(BusinessExceptionCode.CONFIRM_ORDER_FLOW_EXP);
    }


    @Override
    public Integer queryQueue(Long id) {
        ConfirmOrder confirmOrder = confirmorderRepository.getById(id);
        ConfirmOrderStatusEnum statusEnum = ConfirmOrderStatusEnum.getEnumByCode(confirmOrder.getStatus());
        int result = switch (statusEnum){
            case PENDING -> 0;//排队0
            case SUCCESS -> -1;//成功
            case FAILURE -> -2;//失败
            case EMPTY -> -3;//无票
            case CANCEL -> -4;//取消
            case INIT -> 999;//等待->需要查表获得真实数据
            default -> -2;
        };

        if (result == 999){
            return confirmorderRepository.queryQueue(confirmOrder.getTrainCode(), confirmOrder.getCreateTime());
        }

        return result;
    }

    @Override
    public boolean cancelQueue(Long id) {
        ConfirmOrder confirmOrder = confirmorderRepository.getById(id);
        if (!ConfirmOrderStatusEnum.INIT.getCode().equals(confirmOrder.getStatus())) {
            throw new BusinessException("订单正在处理或已处理完成,无法取消!");
        }
        confirmOrder.setStatus(ConfirmOrderStatusEnum.CANCEL.getCode());

        return confirmorderRepository.saveOrUpdate(confirmOrder);
    }
}
